/*
 * Stage.java
 */
package bounce;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Level;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.Timer;

/**
 *
 * @author PANAIMIN
 */
public class Stage extends JPanel implements KeyListener {

    public static final int    GAMES = 8;

    public static Stage Instance() {
        if(_instance == null)
            _instance = new Stage();
        return _instance;
    }

    public void newGame(int level) {
        if(_timer.isRunning())
            _timer.stop();
        _level = level;
        _bat.init();
        _bouncingBalls.clear();
        _fallingPoints.clear();
        _nextBall = new Ball(_bat.center(), 600 - Bat.HIGHT - Ball.RADIUS);
        // read level setting
        URL url = App.class.getResource("levels/" + new Integer(level).toString() + ".csv");
        try {
            InputStream is = url.openStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String line;
            int row = 0;
            _targets = 0;
            while ((line = br.readLine()) != null && row < ROWS) {
                String[] values = line.split(",");
                for(int column = 0; column < values.length && column < COLUMNS; ++column) {
                    int value = Integer.parseInt(values[column]);
                    _blocks[column][row] = value;
                    _points[column][row] = null;
                    if(value > 0) {
                        int point = (int)(Math.random() * 30);
                        if(point < 3) {
                            BonusPoint.FUNCTIONS f = point == 0 ? BonusPoint.FUNCTIONS.EnlargedBat :
                                    point == 1 ? BonusPoint.FUNCTIONS.ExtraBall :
                                    BonusPoint.FUNCTIONS.PassBall;
                            _points[column][row] = new BonusPoint(
                                f,
                                column * BLOCK_WIDTH + BLOCK_WIDTH / 2,
                                row * BLOCK_HIGHT + BLOCK_HIGHT / 2);
                        }
                    }
                    if(value > 0)
                        _targets++;
                    if(row == ROWS - 1) {
                        int leftLimit = COLUMNS / 2;
                        while(leftLimit >= 0 && _blocks[leftLimit][row] >= 0)
                            leftLimit--;
                        _bat.leftLimit((leftLimit + 1) * BLOCK_WIDTH);
                        int rightLimit = COLUMNS / 2;
                        while(rightLimit < COLUMNS && _blocks[rightLimit][row] >= 0)
                            rightLimit++;
                        _bat.rightLimit(rightLimit * BLOCK_WIDTH);
                    }
                }
                row++;
            }
        } catch (IOException ex) {
            App._logger.log(Level.SEVERE, "Failed to read level file");
        }
        _interval = 10 - (_level / 2);
        repaint();
    }

    @Override
    public void paint(Graphics g) {
        for(int x = 0; x < COLUMNS; x++)
            for(int y = 0; y < ROWS; y++) {
                drawBlock(g, x, y);
                if(_points[x][y] != null)
                    _points[x][y].draw(g);
            }
        _bat.draw(g);
        if(_nextBall != null)
            _nextBall.draw(g);
        for(int i = 0; i < _bouncingBalls.size(); ++i)
            _bouncingBalls.get(i).draw(g);
        for(int i = 0; i < _fallingPoints.size(); ++i)
            _fallingPoints.get(i).draw(g);
    }

    private Stage() { }

    private void drawBlock(Graphics g, int x, int y) {
        int value = _blocks[x][y];
        int screenX = x * BLOCK_WIDTH;
        int screenY = y * BLOCK_HIGHT;
        if(value < 0) {
            g.drawImage(_block, screenX, screenY, null);
        } else if(value == 0) {
            g.setColor(_colors[value]);
            g.fillRect(screenX, screenY, BLOCK_WIDTH, BLOCK_HIGHT);
        } else {
            g.setColor(Color.DARK_GRAY);
            g.drawRect(screenX, screenY, BLOCK_WIDTH, BLOCK_HIGHT);
            g.setColor(Color.LIGHT_GRAY);
            g.drawRect(screenX + 1, screenY + 1, BLOCK_WIDTH - 2, BLOCK_HIGHT - 2);
            g.setColor(_colors[value]);
            g.fillRect(screenX + 2, screenY + 2, BLOCK_WIDTH - 4, BLOCK_HIGHT - 4);
        }
    }

    private void repaintBlock(int x, int y) {
        repaint(0, x * BLOCK_WIDTH, y * BLOCK_HIGHT, BLOCK_WIDTH, BLOCK_HIGHT);
    }

    private void checkBlock(Ball ball, int r, int c, boolean up, boolean left, boolean down, boolean right) {
        int v = _blocks[c][r];
        if(v < 0 || (v > 0 && !ball.pass())) {
            if(up)
                ball.down(true);
            else if(down)
                ball.down(false);
            else if(left)
                ball.right(true);
            else if(right)
                ball.right(false);
        }
        if(v > 0) {
            v = ball.pass() ? 0 : v - 1;
            _blocks[c][r] = v;
            if(v == 0 && _points[c][r] != null) {
                _fallingPoints.add(_points[c][r]);
                _points[c][r] = null;
            }
            if(v == 0)
                _targets--;
            repaintBlock(c, r);
        }
    }

    private void checkBlocks(Ball ball) {
        int x = ball.getX();
        int y = ball.getY();
        int row = y / BLOCK_HIGHT;
        int col = x / BLOCK_WIDTH;
        int my = y % BLOCK_HIGHT;
        if(my <= Ball.RADIUS && row > 0 && row <= ROWS)
            checkBlock(ball, row - 1, col, true, false, false, false);
        else if(my >= BLOCK_HIGHT - Ball.RADIUS && row <= ROWS - 2)
            checkBlock(ball, row + 1, col, false, false, true, false);
        int mx = x % BLOCK_WIDTH;
        if(mx <= Ball.RADIUS && col > 0)
            checkBlock(ball, row, col - 1, false, true, false, false);
        else if(mx >= BLOCK_WIDTH - Ball.RADIUS && col <= COLUMNS - 2)
            checkBlock(ball, row, col + 1, false, false, false, true);
    }

    private void step() {
        // step forward each ball
        for(int i = _bouncingBalls.size() - 1; i >= 0; --i) {
            Ball ball = _bouncingBalls.get(i);
            ball.step();
            int x = ball.getX();
            int y = ball.getY();
            boolean missed = false;
            if(x <= Ball.RADIUS)
                ball.right(true);
            else if(x >= 900 - Ball.RADIUS)
                ball.right(false);
            if(y <= Ball.RADIUS)
                ball.down(true);
            else if(y >= 600 - Bat.HIGHT - Ball.RADIUS) {
                if(x < _bat.left() - Ball.RADIUS || x > _bat.right() + Ball.RADIUS) {
                    missed = true;
                    _bouncingBalls.remove(ball);
                } else {
                    ball.down(false);
                }
            }
            if(!missed)
                checkBlocks(ball);
        }
        // check bonus points
        for(int i = _fallingPoints.size() - 1; i >= 0; --i) {
            BonusPoint bp = _fallingPoints.get(i);
            bp.fall();
            if(bp.getY() >= 600 - BonusPoint.RADIUS) {
                int x = bp.getX();
                if(x >= _bat.left() && x <= _bat.right()) {
                    switch(bp.getFunction()) {
                        case ExtraBall:
                            _nextBall = new Ball(_bat.center(), 600 - Bat.HIGHT - Ball.RADIUS);
                            break;
                        case PassBall:
                            for(int b = 0; b < _bouncingBalls.size(); ++b) {
                                Ball ball = _bouncingBalls.get(b);
                                ball.pass(true);
                            }
                            break;
                        case EnlargedBat:
                            _bat.toggle();
                            break;
                    }
                }
                _fallingPoints.remove(bp);
            }
        }
        if(_targets == 0) {
            _timer.stop();
            _extraBallTimer.stop();
            JOptionPane.showMessageDialog(App.Instance(), "Cleared!", "Bounce",
                JOptionPane.INFORMATION_MESSAGE);
            if(_level < GAMES)
                newGame(_level + 1);
        } else if(_bouncingBalls.isEmpty() && _nextBall == null) {
            _timer.stop();
            _extraBallTimer.stop();
            JOptionPane.showMessageDialog(App.Instance(), "Missed! Try again!", "Bounce",
                JOptionPane.ERROR_MESSAGE);
            newGame(_level);
        }
    }

    private static BufferedImage loadImage(String img) {
        URL imgURL = App.class.getResource(img);
        try {
            return ImageIO.read(imgURL);
        } catch (IOException ex) {
            App._logger.log(Level.SEVERE, "Could not load image {0}", img);
        }
        return null;
    }

    private static Stage        _instance;
    private static final int    COLUMNS = 30;
    private static final int    ROWS = 30;
    private static final int    BLOCK_WIDTH = 30;
    private static final int    BLOCK_HIGHT = 20;
    private static final BufferedImage _block = loadImage("images/b1.jpg");
    private static final Color[] _colors = new Color[] {
        Color.BLUE,
        Color.GREEN,
        Color.MAGENTA,
        new Color(192, 192, 0),
        new Color(0, 192, 192),
        new Color(192, 0, 192)
    };

    private int                 _level;
    private int[][]             _blocks = new int[COLUMNS][ROWS];
    private BonusPoint[][]      _points = new BonusPoint[COLUMNS][ROWS];
    private int                 _targets;
    private Bat                 _bat = Bat.Instance();
    private Ball                _nextBall;
    private ArrayList<Ball>     _bouncingBalls = new ArrayList<Ball>();
    private ArrayList<BonusPoint> _fallingPoints = new ArrayList<BonusPoint>();
    private final ActionListener _timerListener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            step();
        }
    };
    private final ActionListener _extraBallListener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            if(_nextBall == null)
              _nextBall = new Ball(_bat.center(), 600 - Bat.HIGHT - Ball.RADIUS);
        }
    };
    private int                 _interval = 5;
    private Timer               _timer = new Timer(_interval, _timerListener);
    private Timer               _extraBallTimer = new Timer(5000, _extraBallListener);

    // KeyListner
    
    @Override
    public void keyTyped(KeyEvent e) { }
    @Override
    public void keyReleased(KeyEvent e) { }
    @Override
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        switch(keyCode) {
            case KeyEvent.VK_LEFT:
                _bat.moveLeft();
                if(_nextBall != null)
                    _nextBall.moveTo(_bat.center(), 600 - Bat.HIGHT - Ball.RADIUS);
                break;
            case KeyEvent.VK_RIGHT:
                _bat.moveRight();
                if(_nextBall != null)
                    _nextBall.moveTo(_bat.center(), 600 - Bat.HIGHT - Ball.RADIUS);
                break;
            case KeyEvent.VK_SPACE:
                if(_nextBall != null) {
                    _bouncingBalls.add(_nextBall);
                    _nextBall = null;
                }
                if(!_timer.isRunning()) {
                    _timer.setDelay(_interval);
                    _timer.start();
                    if(App.Instance()._endlessBall.isSelected())
                      _extraBallTimer.start();
                }
        }
    }
}
